package com.ruoyi.datastatistics.Agreement;

import com.ruoyi.basedata.domain.TerminalsXb;
import com.ruoyi.basedata.domain.vo.ConcentratorCommand;
import com.ruoyi.datastatistics.model.QianBaoTongProactiveModel;
import com.ruoyi.datastatistics.model.QianBaoTongProactiveModel.*;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * 千宝通主动上报协议
 *
 * @author TsingYu
 * @version 1.0
 * @date 2024/11/27 9:13
 */
@SuppressWarnings("unused")
public class QianBaoTongProactive extends QianBaoTong {
    /**
     * 数据域长度L最大字节数
     */
    private final static int MAX_DATA_LENGTH = 1024;
    /**
     * 起始字符
     */
    public final static byte START_BYTE = 0x68;
    /**
     * 结束字符
     */
    public final static byte END_BYTE = 0x16;
    /**
     * 换行符
     */
    public final static byte LINE_BREAKS = 0x0D;
    /**
     * 帧序列域 SEQ 字节长度
     */
    public final static int SEQ_BYTE_LENGTH = 1;
    /**
     * AAH：允许拉闸/关阀；停止自动上报一类数据任务
     */
    public final static byte CLOSE = (byte) 0xAA;
    /**
     * 55H：不允许拉闸/关阀；启动自动上报一类数据任务
     */
    public final static byte OPEN = (byte) 0x55;
    /**
     * 功能码含义
     */
    public final static String[][] FUNC_CODE_MEANING = {
            {"认可", "备用", "备用", "备用", "备用", "备用", "备用", "备用", "用户数据", "否认：无所召唤的数据", "备用", "链路状态", "备用", "备用", "备用", "备用"},
            {"备用", "复位命令", "备用", "备用", "备用", "备用", "备用", "备用", "备用", "链路测试", "请求 1 级数据回复确认帧或否认帧", "请求 2 级数据回复的是数据", "备用", "备用", "备用", "备用"}
    };

    /**
     * 将给定的数据域长度L转成对应的字节数组<br>
     * 两字节数组（D2~D15），采用 BIN编码<br>
     * 整个字节数组顺序如下D7、D6、D5、D4、D3、D2、D1、D0、D15、D14、D13、D12、D11、D10、D9、D8，其中D1、D0位分别位0、1
     *
     * @param length 数据域长度L
     * @return 字节数组
     */
    public static byte[] encodeLength(int length) {
        if (length < 0 || length > MAX_DATA_LENGTH) {
            throw new IllegalArgumentException("长度L不得大于1024");
        }
        byte[] bytes = new byte[2];
        String binaryString = Integer.toBinaryString(length) + "01";
        binaryString = String.format("%16s", binaryString).replace(' ', '0');
        // 小端字节序，从右往左
        int byte0 = Integer.parseInt(binaryString.substring(8, 16), 2);
        int byte1 = Integer.parseInt(binaryString.substring(0, 8), 2);
        bytes[0] = (byte) byte0;
        bytes[1] = (byte) byte1;
        return bytes;
    }


    /**
     * 从数据域长度字节数组中获取数据域字节数
     *
     * @param lengthByteArray 数据域长度字节数组
     * @return 数据域字节数
     */
    public static int decodeLength(byte[] lengthByteArray) {
        // 小端字节序，从右往左
        int byte0 = lengthByteArray[1] & 0xFF;
        int byte1 = lengthByteArray[0] & 0xFF;
        String binaryString = toBinaryString(8, byte0) + toBinaryString(8, byte1);
        return Integer.parseInt(binaryString.substring(0, 14), 2);
    }

    /**
     * 将控制码编码成字节数组
     *
     * @param code 控制码
     * @return 字节数组
     */
    public static byte[] encodeControlCode(ControlCode code) {
        byte[] controlCodeByteArray = new byte[1];
        String binaryString = code.getDir() + code.getPrm() + "11" + toBinaryString(4, code.getFuncCode() & 0xFF);
        controlCodeByteArray[0] = (byte) Integer.parseInt(binaryString, 2);
        return controlCodeByteArray;
    }


    /**
     * 解析出控制码
     *
     * @param controlCodeByteArray 控制码字节数组
     * @return 控制码
     */
    public static ControlCode decodeControlCode(byte[] controlCodeByteArray) {
        // 控制码1字节
        byte controlCodeByte = controlCodeByteArray[0];
        String binaryString = toBinaryString(8, controlCodeByte & 0xFF);
        byte dir = Byte.parseByte(binaryString.substring(0, 1));
        byte prm = Byte.parseByte(binaryString.substring(1, 2));
        byte funcCode = Byte.parseByte(binaryString.substring(4, 8), 2);
        String funcCodeMeaning = FUNC_CODE_MEANING[prm][funcCode];
        ControlCode controlCode = new ControlCode();
        controlCode.setRaw(controlCodeByteArray);
        controlCode.setDir(dir);
        controlCode.setPrm(prm);
        controlCode.setFuncCode(funcCode);
        controlCode.setFuncCodeMeaning(funcCodeMeaning);
        return controlCode;
    }

    /**
     * 编码地址为字节数组
     *
     * @param address 地址
     * @return 字节数组
     */
    public static byte[] encodeAddress(Address address) {
        byte[] result = new byte[5];
        byte[] areaCode = reverse(convertToBcd(address.getAreaCode()));
        System.arraycopy(areaCode, 0, result, 0, areaCode.length);
        String binaryString = toBinaryString(16, address.getTerminalAddr());
        // 小端字节序
        result[2] = (byte) Integer.parseInt(binaryString.substring(8, 16), 2);
        result[3] = (byte) Integer.parseInt(binaryString.substring(0, 8), 2);
        result[4] = address.getAddressFlag();
        return result;
    }

    /**
     * 解码字节数组为地址对象
     *
     * @param addressByteArray 地址字节数组
     * @return 地址对象
     */
    public static Address decodeAddress(byte[] addressByteArray) {
        Address address = new Address();
        address.setRaw(addressByteArray);
        byte[] areaCodeRaw = Arrays.copyOfRange(addressByteArray, 0, 2);
        address.setAreaCodeRaw(areaCodeRaw);
        String areaCode = Hex.encodeHexString(reverse(areaCodeRaw));
        address.setAreaCode(areaCode);
        byte[] terminalAddrRaw = Arrays.copyOfRange(addressByteArray, 2, 4);
        address.setTerminalAddrRaw(terminalAddrRaw);
        // 小端字节序
        int terminalAddr = Integer.parseInt(toBinaryString(8, terminalAddrRaw[1] & 0xFF) + toBinaryString(8, terminalAddrRaw[0] & 0xFF), 2);
        address.setTerminalAddr(terminalAddr);
        byte addressFlag = Arrays.copyOfRange(addressByteArray, 4, 5)[0];
        address.setAddressFlag(addressFlag);
        if (terminalAddr == Address.TERMINAL_ADDRESS_MAX_VALUE && (addressFlag & Address.ADDRESS_FLAG_LOWEST_BIT) == 1) {
            // terminalAddr=FFFFH且addressFlag的D0位为“1”时表示系统广播地址。
            address.setSystemBroadcastFlag(true);
        } else if (terminalAddr == Address.TERMINAL_ADDRESS_INVALID_VALUE) {
            // terminalAddr=0000H为无效地址
            address.setInvalidAddress(true);
        }
        String addr = String.format("%s%05d", areaCode, terminalAddr);
        address.setAddress(addr);
        return address;
    }

    /**
     * 解析帧序列域 SEQ
     *
     * @param seqFrame 帧序列域 SEQ
     * @return {@link Seq}
     */
    public static Seq decodeSeq(byte[] seqFrame) {
        if (seqFrame == null || seqFrame.length != SEQ_BYTE_LENGTH) {
            throw new IllegalArgumentException(String.format("无效的参数，SEQ长度应该为 %s", SEQ_BYTE_LENGTH));
        }
        // 帧序列域 SEQ为 1字节
        byte seqByte = seqFrame[0];
        Seq seq = new Seq();
        seq.setRaw(seqFrame);
        // 按二进制拆分
        String seqBinaryString = toBinaryString(8, seqByte & 0xFF);
        // 帧序号
        String sequenceStr = seqBinaryString.substring(4, 8);
        seq.setSeq(Byte.parseByte(sequenceStr, 2));
        // CON
        seq.setCon(Byte.parseByte(seqBinaryString.substring(3, 4)));
        // FIN
        seq.setFin(Byte.parseByte(seqBinaryString.substring(2, 3)));
        // FIR
        seq.setFir(Byte.parseByte(seqBinaryString.substring(1, 2)));
        return seq;
    }

    /**
     * 编码帧序列域 SEQ
     *
     * @param seq 帧序列域 SEQ
     * @return 字节数组
     */
    public static byte[] encodeSeq(Seq seq) {
        // 帧序列域 SEQ为 1字节
        byte[] seqBytes = new byte[SEQ_BYTE_LENGTH];
        String binaryBuilder = "0" +
                // FIR
                seq.getFir() +
                // FIN
                seq.getFin() +
                // CON
                seq.getCon() +
                // 帧序号
                toBinaryString(4, seq.getSeq());
        seqBytes[0] = (byte) Integer.parseInt(binaryBuilder, 2);
        return seqBytes;
    }

    /**
     * 从整个数据帧中解析出共同的部分
     *
     * @param fullByteArray 整个数据帧
     * @param <T>           数据单元类型
     * @return 数据模型 {@link QianBaoTongProactiveModel}
     */
    public static <T> QianBaoTongProactiveModel<T> decodeCommonPart(byte[] fullByteArray) {
        // 有换行符的话去掉
        if (fullByteArray[fullByteArray.length - 1] == LINE_BREAKS) {
            fullByteArray = Arrays.copyOfRange(fullByteArray, 0, fullByteArray.length - 1);
        }
        QianBaoTongProactiveModel<T> model = new QianBaoTongProactiveModel<>();
        model.setStart(START_BYTE);
        byte[] lengthRaw = Arrays.copyOfRange(fullByteArray, 1, 3);
        model.setLength(decodeLength(lengthRaw));
        model.setLengthRaw(lengthRaw);
        model.setControlCode(decodeControlCode(Arrays.copyOfRange(fullByteArray, 6, 7)));
        model.setAddress(decodeAddress(Arrays.copyOfRange(fullByteArray, 7, 12)));
        model.setAfn(Afn.getAfnByFnCode(fullByteArray[12]));
        model.setSeq(decodeSeq(Arrays.copyOfRange(fullByteArray, 13, 14)));
        model.setFn(new Fn(Arrays.copyOfRange(fullByteArray, 14, 18)));
        // 数据单元
        byte[] dataUnit = Arrays.copyOfRange(fullByteArray, 18, fullByteArray.length - 2);
        model.setDataUnitRaw(dataUnit);
        model.setCheckSum(fullByteArray[fullByteArray.length - 2]);
        model.setEnd(END_BYTE);
        return model;
    }

    /**
     * 解析出集中器版本信息
     *
     * @param dataUnit 数据单元字节数组
     * @return 集中器版本信息
     */
    public static Concentrator decodeConcentratorVersion(byte[] dataUnit) {
        Concentrator concentrator = new Concentrator();
        concentrator.setRaw(dataUnit);
        // 软件版本
        byte[] softwareVerBytes = Arrays.copyOfRange(dataUnit, 0, 8);
        concentrator.setSoftwareVerRaw(softwareVerBytes);
        concentrator.setSoftwareVer(decodeConcentratorVer(softwareVerBytes));
        // 硬件版本
        byte[] hardwareVerBytes = Arrays.copyOfRange(dataUnit, 8, 16);
        concentrator.setHardwareVerRaw(hardwareVerBytes);
        concentrator.setHardwareVer(decodeConcentratorVer(hardwareVerBytes));
        return concentrator;
    }

    /**
     * 解析集中器版本信息，返回格式CCXX-AITT-FF-NNNNNN
     *
     * @param concentratorVerBytes 集中器版本信息字节数组
     * @return 集中器版本
     */
    private static String decodeConcentratorVer(byte[] concentratorVerBytes) {
        if (!isNotEmptyData(concentratorVerBytes)) {
            return null;
        }
        StringBuilder stringBuilder = new StringBuilder();
        concentratorVerBytes = reverse(concentratorVerBytes);
        // 第一个字节-厂商代码CC
        byte byte1 = concentratorVerBytes[0];
        String binStr1 = toBinaryString(8, byte1 & 0xFF);
        stringBuilder.append(Integer.parseInt(binStr1.substring(0, 4), 2));
        stringBuilder.append(Integer.parseInt(binStr1.substring(4, 8), 2));
        // 第二个字节-不同类型的软件XX
        byte byte2 = concentratorVerBytes[1];
        String binStr2 = toBinaryString(8, byte2 & 0xFF);
        stringBuilder.append(Integer.parseInt(binStr2.substring(0, 4), 2));
        stringBuilder.append(Integer.parseInt(binStr2.substring(4, 8), 2));
        stringBuilder.append("-");
        // 第三个字节-A主版本、I次版本
        byte byte3 = concentratorVerBytes[2];
        String binStr3 = toBinaryString(8, byte3 & 0xFF);
        stringBuilder.append(Integer.parseInt(binStr3.substring(0, 4), 2));
        stringBuilder.append(Integer.parseInt(binStr3.substring(4, 8), 2));
        // 第四个字节-T小版本
        byte byte4 = concentratorVerBytes[3];
        String binStr4 = toBinaryString(8, byte4 & 0xFF);
        stringBuilder.append(Integer.parseInt(binStr4.substring(0, 4), 2));
        stringBuilder.append(Integer.parseInt(binStr4.substring(4, 8), 2));
        stringBuilder.append("-");
        // 第五个字节-附加信息F
        byte byte5 = concentratorVerBytes[4];
        String binStr5 = toBinaryString(8, byte5 & 0xFF);
        stringBuilder.append(Integer.parseInt(binStr5.substring(0, 4), 2));
        stringBuilder.append(Integer.parseInt(binStr5.substring(4, 8), 2));
        stringBuilder.append("-");
        // 第六个字节-行政区码NN
        byte byte6 = concentratorVerBytes[5];
        String binStr6 = toBinaryString(8, byte6 & 0xFF);
        stringBuilder.append(Integer.parseInt(binStr6.substring(0, 4), 2));
        stringBuilder.append(Integer.parseInt(binStr6.substring(4, 8), 2));
        // 第七个字节-行政区码NN
        byte byte7 = concentratorVerBytes[6];
        String binStr7 = toBinaryString(8, byte7 & 0xFF);
        stringBuilder.append(Integer.parseInt(binStr7.substring(0, 4), 2));
        stringBuilder.append(Integer.parseInt(binStr7.substring(4, 8), 2));
        // 第八个字节-行政区码NN
        byte byte8 = concentratorVerBytes[7];
        String binStr8 = toBinaryString(8, byte8 & 0xFF);
        stringBuilder.append(Integer.parseInt(binStr8.substring(0, 4), 2));
        stringBuilder.append(Integer.parseInt(binStr8.substring(4, 8), 2));
        return stringBuilder.toString();
    }

    /**
     * 编码请求集中器时钟、集中器版本号命令
     *
     * @param address 集中器地址
     * @param fn      pn：0；fn：F1-集中器时钟、F9-集中器版本号
     * @return 命令字节数组
     */
    public static byte[] encodeConcentratorInfo(Address address, Fn fn) {
        byte[] result = new byte[20];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(12);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.REQUESTING.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    /**
     * 编码下载档案命令
     *
     * @param deviceProfileList 需要下载档案的表信息
     * @param address           集中器地址
     * @return 字节数组List
     */
    public static List<byte[]> encodeDownloadProfileCommand(List<DeviceProfile> deviceProfileList, Address address) {
        // 每帧最多包含10个表
        List<List<DeviceProfile>> deviceProfileLists = ListUtils.partition(deviceProfileList, 10);
        List<byte[]> finalResult = new ArrayList<>(deviceProfileLists.size());
        for (int i = 0; i < deviceProfileLists.size(); i++) {
            List<DeviceProfile> profileList = deviceProfileLists.get(i);
            int deviceQty = profileList.size();
            // 固定长度的报文头6字节+控制域1字节+地址域5字节+应用层功能码 AFN 1字节+帧序列域 SEQ 1字节+数据单元标识 Fn 4字节+本次配置的居民水电气数量n 2字节+每个档案22字节+校验和1字节+结束符1字节
            byte[] result = new byte[6 + 1 + 5 + 1 + 1 + 4 + 2 + deviceQty * 22 + 2];
            result[0] = START_BYTE;
            byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 2 + deviceQty * 22);
            System.arraycopy(lengthBytes, 0, result, 1, 2);
            System.arraycopy(lengthBytes, 0, result, 3, 2);
            result[5] = START_BYTE;
            ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
            byte[] cc = encodeControlCode(controlCode);
            result[6] = cc[0];
            byte[] addr = encodeAddress(address);
            System.arraycopy(addr, 0, result, 7, 5);
            result[12] = Afn.SETTING.getFnCode();
            Seq seq;
            if (deviceProfileLists.size() == 1) {
                // 单帧
                seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) i).build();
            } else if (i == 0) {
                // 多帧：第 1帧，有后续帧
                seq = Seq.builder().fir((byte) 1).fin((byte) 0).con((byte) 1).seq((byte) i).build();
            } else if (deviceProfileLists.size() - 1 == i) {
                // 多帧：结束帧
                seq = Seq.builder().fir((byte) 0).fin((byte) 1).con((byte) 1).seq((byte) i).build();
            } else {
                // 多帧：中间帧
                seq = Seq.builder().fir((byte) 0).fin((byte) 0).con((byte) 1).seq((byte) i).build();
            }
            byte[] seqBytes = encodeSeq(seq);
            System.arraycopy(seqBytes, 0, result, 13, 1);
            Fn fn = new Fn(0, 1);
            System.arraycopy(fn.getRaw(), 0, result, 14, 4);
            // 本次配置的居民水电气数量deviceQty
            byte[] deviceQtyBytes = new byte[2];
            deviceQtyBytes[0] = (byte) deviceQty;
            System.arraycopy(deviceQtyBytes, 0, result, 18, 2);
            for (int j = 0; j < deviceQty; j++) {
                DeviceProfile profile = profileList.get(j);
                // 表序号
                String seqBin = toBinaryString(16, profile.getSeq());
                byte[] ordinal = new byte[2];
                ordinal[0] = (byte) Integer.parseInt(seqBin.substring(8, 16), 2);
                ordinal[1] = (byte) Integer.parseInt(seqBin.substring(0, 8), 2);
                System.arraycopy(ordinal, 0, result, 20 + j * 22, 2);
                // 表号
                byte[] meterNumber = toData03(profile.getMeterNumber());
                System.arraycopy(meterNumber, 0, result, 20 + j * 22 + 2, 6);
                // 测量点性质
                result[20 + j * 22 + 2 + 6] = toData06(profile.getMeasuringPointProperties());
                // 接线方式
                result[20 + j * 22 + 2 + 6 + 1] = toData07(profile.getWiringType());
                // 电表费率号
                result[20 + j * 22 + 2 + 6 + 1 + 1] = toData08(profile.getMeterFeeRateNumber());
                // 表类型代码
                result[20 + j * 22 + 2 + 6 + 1 + 1 + 1] = toData09(profile.getMeterTypeCode());
                // 线路编号
                System.arraycopy(toData10(profile.getLineNumber()), 0, result, 20 + j * 22 + 2 + 6 + 1 + 1 + 1 + 1, 2);
                // 表箱编号
                String boxNumberBinaryString = toBinaryString(16, profile.getBoxNumber());
                result[20 + j * 22 + 2 + 6 + 1 + 1 + 1 + 1 + 2] = (byte) Integer.parseInt(boxNumberBinaryString.substring(8, 16), 2);
                result[20 + j * 22 + 2 + 6 + 1 + 1 + 1 + 1 + 3] = (byte) Integer.parseInt(boxNumberBinaryString.substring(0, 8), 2);
                // 采集模块编号
                byte[] collectingModuleNumber = toData03(profile.getCollectingModuleNumber());
                System.arraycopy(collectingModuleNumber, 0, result, 20 + j * 22 + 2 + 6 + 1 + 1 + 1 + 1 + 4, 6);
            }
            byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
            result[result.length - 2] = checkSum;
            result[result.length - 1] = END_BYTE;
            finalResult.add(result);
        }
        return finalResult;
    }

    /**
     * 编码单抄命令
     *
     * @param copyCommand 抄表信息
     * @param address     集中器地址
     * @return 字节数组
     */
    public static byte[] encodeSingleCopyCommand(Copy copyCommand, Address address) {
        // 固定长度的报文头6字节+控制域1字节+地址域5字节+应用层功能码 AFN 1字节+帧序列域 SEQ 1字节+数据单元标识 Fn 4字节+抄读方式 1字节+集中器上传的水电气数 2字节+每个序号2字节+校验和1字节+结束符1字节
        byte[] result = new byte[6 + 1 + 5 + 1 + 1 + 4 + 1 + 2 + copyCommand.getMeterQty() * 2 + 2];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 1 + 2 + copyCommand.getMeterQty() * 2);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.REQUESTING.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        Fn fn = new Fn(0, 57);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        // 抄读方式
        result[18] = copyCommand.getCopyType();
        // 集中器上传的水电气数
        String meterQtyBin = toBinaryString(16, copyCommand.getMeterQty());
        byte[] meterQty = new byte[2];
        meterQty[0] = (byte) Integer.parseInt(meterQtyBin.substring(8, 16), 2);
        meterQty[1] = (byte) Integer.parseInt(meterQtyBin.substring(0, 8), 2);
        System.arraycopy(meterQty, 0, result, 19, 2);
        for (int i = 0; i < copyCommand.getMeterData().length; i++) {
            MeterData meterData = copyCommand.getMeterData()[i];
            // 表序号
            String seqBin = toBinaryString(16, meterData.getSeq());
            byte[] ordinal = new byte[2];
            ordinal[0] = (byte) Integer.parseInt(seqBin.substring(8, 16), 2);
            ordinal[1] = (byte) Integer.parseInt(seqBin.substring(0, 8), 2);
            System.arraycopy(ordinal, 0, result, 21 + i * 2, 2);
        }
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }


    /**
     * 解析抄表数据
     *
     * @param copyDataBytes 单抄命令上行数据单元部分
     * @return 抄表数据
     */
    public static Copy decodeCopyData(byte[] copyDataBytes) {
        Copy copy = new Copy();
        // 表数量字节
        byte[] meterQty = Arrays.copyOfRange(copyDataBytes, 0, 2);
        String meterQtyBinStr = toBinaryString(8, meterQty[1] & 0xFF) + toBinaryString(8, meterQty[0] & 0xFF);
        int qty = Integer.parseInt(meterQtyBinStr, 2);
        copy.setMeterQty(qty);
        copy.setMeterQtyRaw(meterQty);
        MeterData[] meterDatas = new MeterData[qty];
        // 表数据总字节数，每个表序号2字节+累计水量(4字节)+阀门状态(1字节，0开1关)
        for (int i = 0; i < qty; i++) {
            MeterData meterData = new MeterData();
            byte[] seqBytes = Arrays.copyOfRange(copyDataBytes, 2 + i * 7, 4 + i * 7);
            String seqBinStr = toBinaryString(8, seqBytes[1] & 0xFF) + toBinaryString(8, seqBytes[0] & 0xFF);
            meterData.setSeqRaw(seqBytes);
            meterData.setSeq(Integer.parseInt(seqBinStr, 2));
            byte[] totalFlowBytes = Arrays.copyOfRange(copyDataBytes, 4 + i * 7, 8 + i * 7);
            if (isNotEmptyData(totalFlowBytes)) {
                BigDecimal totalFlow = convertData02(totalFlowBytes);
                meterData.setTotalFlowRaw(totalFlowBytes);
                meterData.setTotalFlow(totalFlow);
            }
            byte[] valveStateBytes = Arrays.copyOfRange(copyDataBytes, 8 + i * 7, 9 + i * 7);
            meterData.setValveState(valveStateBytes[0]);
            meterDatas[i] = meterData;
        }
        copy.setMeterData(meterDatas);
        return copy;
    }

    /**
     * 集中器对表拉合闸/开关阀控制命令
     *
     * @param address   集中器地址
     * @param password  管理员密码（高级权限）（给电表）（默认123456）
     * @param meterSeq  表序号
     * @param relayMode 中继方式。<code>00H</code>：表示自动中继，<code>01H</code>：表示固定中继
     * @param secretKey 由集中器管理并与电表配合使用实现重要信息的安全设置
     * @param relaySeq1 （载波）中继序号 1。 抄读方式为 <code>00H</code> 时无效，字节长度为 0
     * @param relaySeq2 （载波）中继序号 2。 抄读方式为 <code>00H</code> 时无效，字节长度为 0
     * @param relaySeq3 （载波）中继序号 3。 抄读方式为 <code>00H</code> 时无效，字节长度为 0
     * @param open      false：关，true：开
     * @return byte[]
     */
    public static byte[] encodeValveControl(Address address, String password, int meterSeq, byte relayMode, String secretKey, Integer relaySeq1, Integer relaySeq2, Integer relaySeq3, boolean open) {
        byte[] result;
        if (relayMode == 1) {
            // 固定中继，中继序号必传
            if (relaySeq1 == null || relaySeq2 == null || relaySeq3 == null) {
                throw new IllegalArgumentException("无效的中继序号");
            }
            result = new byte[37];
            //（载波）中继序号 1
            String relaySeq1Bin = toBinaryString(16, relaySeq1);
            byte[] relaySeq1Raw = new byte[]{(byte) (Integer.parseInt(relaySeq1Bin.substring(8, 16), 2)), (byte) (Integer.parseInt(relaySeq1Bin.substring(0, 8), 2))};
            System.arraycopy(relaySeq1Raw, 0, result, 28, 2);
            //（载波）中继序号 2
            String relaySeq2Bin = toBinaryString(16, relaySeq2);
            byte[] relaySeq2Raw = new byte[]{(byte) (Integer.parseInt(relaySeq2Bin.substring(8, 16), 2)), (byte) (Integer.parseInt(relaySeq2Bin.substring(0, 8), 2))};
            System.arraycopy(relaySeq2Raw, 0, result, 30, 2);
            //（载波）中继序号 3
            String relaySeq3Bin = toBinaryString(16, relaySeq3);
            byte[] relaySeq3Raw = new byte[]{(byte) (Integer.parseInt(relaySeq3Bin.substring(8, 16), 2)), (byte) (Integer.parseInt(relaySeq3Bin.substring(0, 8), 2))};
            System.arraycopy(relaySeq3Raw, 0, result, 32, 2);
        } else {
            // 自动中继
            result = new byte[31];
        }
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 3 + 2 + 4 + 1 + 1);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.CONTROL.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        Fn fn = new Fn(0, 1);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        // 管理员密码（高级权限）
        byte[] pwd = reverse(convertToBcd(password));
        System.arraycopy(fnBytes, 0, result, 18, 3);
        // 表序号
        String seqBin = toBinaryString(16, meterSeq);
        byte[] ordinal = new byte[2];
        ordinal[0] = (byte) Integer.parseInt(seqBin.substring(8, 16), 2);
        ordinal[1] = (byte) Integer.parseInt(seqBin.substring(0, 8), 2);
        System.arraycopy(ordinal, 0, result, 21, 2);
        // 密钥
        if (StringUtils.isNotBlank(secretKey)) {
            System.arraycopy(reverse(convertToBcd(secretKey)), 0, result, 23, 4);
        }
        // 中继方式
        result[27] = relayMode;
        // 控制字
        result[result.length - 3] = open ? OPEN : CLOSE;
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }


    /**
     * 阀门控制。已弃用，请使用{@link #encodeValveControl(Address, String, int, byte, String, Integer, Integer, Integer, boolean)}
     *
     * @param address   集中器地址
     * @param meterSeq  表序号
     * @param open      false：关，true：开
     * @param secretKey 密钥
     * @return byte[]
     */
    @Deprecated
    public static byte[] encodeValveControl(Address address, int meterSeq, String secretKey, boolean open) {
        byte[] result = new byte[27];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 2 + 4 + 1);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.CONTROL.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        Fn fn = new Fn(0, 2);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        // 表序号
        String seqBin = toBinaryString(16, meterSeq);
        byte[] ordinal = new byte[2];
        ordinal[0] = (byte) Integer.parseInt(seqBin.substring(8, 16), 2);
        ordinal[1] = (byte) Integer.parseInt(seqBin.substring(0, 8), 2);
        System.arraycopy(ordinal, 0, result, 18, 2);
        // 密钥
        if (StringUtils.isNotBlank(secretKey)) {
            System.arraycopy(reverse(convertToBcd(secretKey)), 0, result, 20, 4);
        }
        // 开关阀
        result[24] = open ? OPEN : CLOSE;
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    /**
     * 设置集中器地址
     *
     * @param oldAddress 原来的集中器地址
     * @param newAddress 新的集中器地址
     * @return byte[]
     */
    public static byte[] encodeSetConcentratorAddr(Address oldAddress, Address newAddress) {
        byte[] result = new byte[24];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 4);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(oldAddress);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.CONTROL.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        Fn fn = new Fn(0, 2);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        // 新的集中器地址，只要行政区划码A1+终端地址A2 4字节
        System.arraycopy(encodeAddress(newAddress), 0, result, 18, 4);
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    /**
     * 解析心跳、退出登录或登录
     *
     * @param dataUnit 数据单元部分
     * @return 信号强度
     */
    public static BigDecimal decodeHeartBeatOrLogin(byte[] dataUnit) {
        if (isNotEmptyData(dataUnit)) {
            // 信号强度
            return new BigDecimal(Hex.encodeHexString(dataUnit));
        } else {
            return null;
        }

    }

    /**
     * 设置自动上报一类数据任务启动/停止
     *
     * @param address 集中器地址
     * @param start   true：启动、false：停止
     * @return 命令字节数组
     */
    public static byte[] encodeAutoReporting(Address address, boolean start) {
        byte[] result = new byte[21];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 1);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.SETTING.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        Fn fn = new Fn(0, 31);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        // 启动或停止自动上报一类数据任务
        result[18] = start ? OPEN : CLOSE;
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    /**
     * 设置自动上报一类数据任务时间
     *
     * @param address  集中器地址
     * @param dateTime 发送基准时间：秒分时日月年。集中器以此设定的时间开始发送，并按定时发送周期循环。
     * @param interval 定时发送周期。定时发送数据的时间周期。
     * @param timeUnit 定时发送周期单位。只能是月{@link ChronoUnit#MONTHS}、日{@link ChronoUnit#DAYS}或者时{@link ChronoUnit#HOURS}
     * @return 命令字节数组
     */
    public static byte[] encodeAutoReportingTime(Address address, LocalDateTime dateTime, int interval, ChronoUnit timeUnit) {
        if (timeUnit != ChronoUnit.MONTHS && timeUnit != ChronoUnit.DAYS && timeUnit != ChronoUnit.HOURS) {
            throw new IllegalArgumentException("定时发送周期单位只能时月、日或者时");
        }
        // 定时发送周期只有6位，因此最大值是111111B，即63
        int intervalMax = 63;
        if (interval <= 0 || interval > intervalMax) {
            throw new IllegalArgumentException("定时发送周期必须大于0小于等于63");
        }
        byte[] result = new byte[27];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 7);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.SETTING.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        Fn fn = new Fn(1, 30);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        // 定时发送周期单位和定时发送周期组成的八位二进制数字符串
        String bin;
        // 定时发送周期单位用D6~D7表示。
        if (timeUnit == ChronoUnit.MONTHS) {
            // D7:1,D6:1表示月
            bin = "11";
        } else if (timeUnit == ChronoUnit.DAYS) {
            // D7:1,D6:0表示日
            bin = "10";
        } else {
            // D7:0,D6:1表示时
            bin = "01";
        }
        // 定时发送周期用D0~D5表示，为定时发送数据的时间周期。
        String intervalBin = toBinaryString(6, interval);
        bin = bin + intervalBin;
        result[18] = (byte) Integer.parseInt(bin, 2);
        // 发送基准时间：秒分时日月年
        byte[] time = toData01(dateTime);
        System.arraycopy(time, 0, result, 19, 6);
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    /**
     * 确认∕否认报文是对接收报文中需要被确认（CON=1）的回答，以及集中器对所请求
     * 的数据不具备响应条件的否认回答。该报文为单帧报文，帧序列域的标志位 FIR=1，FIN=1，CON=0。
     *
     * @param address 集中器地址
     * @param confirm true：确认、false：否认
     * @return 命令字节数组
     */
    public static byte[] encodeResponseConfirm(Address address, boolean confirm) {
        byte[] result = new byte[20];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 10).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.CONFIRM.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 0).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        // F3 心跳：对收到报文中的全部数据单元标识进行确认；F2 退出登录：对收到报文中的全部数据单元标识进行否认
        Fn fn = confirm ? new Fn(0, 3) : new Fn(0, 2);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }


    /**
     * 链路接口检测命令报文回复确认/否认
     *
     * @param address      集中器地址
     * @param confirmedAfn 被确认/否认的报文的Afn
     * @param confirmedFn  被确认/否认的报文的Fn
     * @param confirm      true：确认、false：否认
     * @return 命令字节数组
     */
    public static byte[] encodeResponseConfirm(Address address, Afn confirmedAfn, Fn confirmedFn, boolean confirm) {
        byte[] result = new byte[25];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 5);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 0).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.CONFIRM.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 0).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        // F1 全部确认：对收到报文中的全部数据单元标识进行确认；F2 全部否认：对收到报文中的全部数据单元标识进行否认
        Fn fn = confirm ? new Fn(0, 1) : new Fn(0, 2);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        // 被确认/否认报文的Afn
        result[18] = confirmedAfn.getFnCode();
        // 被确认/否认报文的Fn
        System.arraycopy(fn.getRaw(), 0, result, 19, 4);
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    /**
     * 复位命令
     *
     * @param address 集中器地址
     * @param fn      pn固定为0。fn可选：F1 硬件初始化；F2 数据区初始化；F3 参数及全体数据区初始化
     * @return byte[]
     */
    public static byte[] encodeResetCommand(Address address, Fn fn) {
        byte[] result = new byte[20];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 1).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.RESET.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        // F1 硬件初始化；F2 数据区初始化；F3 参数及全体数据区初始化
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    /**
     * 集中器校时命令
     *
     * @param address       集中器地址
     * @param localDateTime 时间
     * @return byte[]
     */
    public static byte[] encodeCorrectClock(Address address, LocalDateTime localDateTime) {
        byte[] result = new byte[26];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4 + 6);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.CONTROL.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        Fn fn = new Fn(0, 13);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        // 时间
        System.arraycopy(toData01(localDateTime), 0, result, 18, 6);
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    /**
     * 水电气集抄查询参数
     *
     * @param address 集中器地址
     * @param fn      pn：0。fn：F7-主站/集中器 IP地址和端口；F10-集中器下行通信参数抄读；F30-抄读自动上报一类数据任务参数；F31-抄读自动上报一类数据任务启动/停止状态
     * @return byte[]
     */
    public static byte[] encodeQueryParams(Address address, Fn fn) {
        byte[] result = new byte[20];
        result[0] = START_BYTE;
        byte[] lengthBytes = encodeLength(1 + 5 + 1 + 1 + 4);
        System.arraycopy(lengthBytes, 0, result, 1, 2);
        System.arraycopy(lengthBytes, 0, result, 3, 2);
        result[5] = START_BYTE;
        ControlCode controlCode = ControlCode.builder().dir((byte) 0).prm((byte) 1).funcCode((byte) 0).build();
        byte[] cc = encodeControlCode(controlCode);
        result[6] = cc[0];
        byte[] addr = encodeAddress(address);
        System.arraycopy(addr, 0, result, 7, 5);
        result[12] = Afn.QUERY.getFnCode();
        Seq seq = Seq.builder().fir((byte) 1).fin((byte) 1).con((byte) 1).seq((byte) 0).build();
        byte[] seqBytes = encodeSeq(seq);
        System.arraycopy(seqBytes, 0, result, 13, 1);
        byte[] fnBytes = fn.getRaw();
        System.arraycopy(fnBytes, 0, result, 14, 4);
        byte checkSum = checkSum(Arrays.copyOfRange(result, 6, result.length - 2));
        result[result.length - 2] = checkSum;
        result[result.length - 1] = END_BYTE;
        return result;
    }

    public static void main(String[] args) {
        // Address address = new Address("130300001", false);
        // DeviceProfile profile = new DeviceProfile();
        // profile.setSeq(1);
        // profile.setMeterNumber("852024101105");
        // profile.setMeasuringPointProperties(new MeasuringPointProperties(MeasuringPointPropertiesG.COLD_WATER_METER, MeasuringPointPropertiesH.MBUS_METER));
        // profile.setWiringType(new WiringType(WiringTypeG.SINGLE_PHASE_METER, WiringTypeS.DIRECT));
        // profile.setMeterTypeCode(new MeterTypeCode((byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0));
        // profile.setBoxNumber((short) 0);
        // profile.setCollectingModuleNumber("000000000001");
        // DeviceProfile profile1 = new DeviceProfile();
        // profile1.setSeq(2);
        // profile1.setMeterNumber("852024101106");
        // profile1.setMeasuringPointProperties(new MeasuringPointProperties(MeasuringPointPropertiesG.COLD_WATER_METER, MeasuringPointPropertiesH.MBUS_METER));
        // profile1.setWiringType(new WiringType(WiringTypeG.SINGLE_PHASE_METER, WiringTypeS.DIRECT));
        // profile1.setMeterTypeCode(new MeterTypeCode((byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0));
        // profile1.setBoxNumber((short) 0);
        // profile1.setCollectingModuleNumber("000000000001");
        // encodeDownloadProfileCommand(Arrays.asList(profile, profile1), address).forEach(e -> System.out.println("下载档案：" + Hex.encodeHexString(e, false)));
        // Copy copy = new Copy();
        // copy.setCopyType((byte) 0x01);
        // copy.setMeterQty(2);
        // MeterData meterData = new MeterData();
        // meterData.setSeq(1);
        // MeterData meterData1 = new MeterData();
        // meterData1.setSeq(2);
        // copy.setMeterData(new MeterData[]{meterData, meterData1});
        // System.out.println("抄表命令：" + Hex.encodeHexString(encodeSingleCopyCommand(copy, address)));
        // QianBaoTongProactiveModel<Object> model = decodeCommonPart(objectToByteArray("68c500c500688803130100008c600000010705000500000000000004000000000000010000230100010200000000000103002600000000f316"));
        //
        // Copy copy1 = decodeCopyData(model.getDataUnitRaw());
        // model.setDataUnit(copy1);
        // // LocalDateTime time = convertData01(model.getDataUnitRaw());
        // // model.setDataUnit(time);
        // System.out.println("抄表数据Raw：" + Hex.encodeHexString(model.getDataUnitRaw()));
        // System.out.println("抄表数据：" + model);
        // System.out.println("重置命令：" + Hex.encodeHexString(encodeResetCommand(address, new Fn(0, 2))));
        // System.out.println("阀控命令：" + Hex.encodeHexString(encodeValveControl(new Address("130300001", false), "123456", 2, (byte) 0, "01111111", null, null, null, false)));
        // LocalDateTime dateTime = LocalDateTime.of(2024, 12, 20, 10, 10, 0);
        // System.out.println("自动上报时间：" + Hex.encodeHexString(encodeAutoReportingTime(address, dateTime, 1, ChronoUnit.HOURS)));
        // System.out.println("自动上报启停：" + Hex.encodeHexString(encodeAutoReporting(address, true)));
        // System.out.println("集中器时间命令：" + Hex.encodeHexString(encodeConcentratorInfo(address, new Fn(0, 1))));
        // System.out.println("集中器校时命令：" + Hex.encodeHexString(encodeCorrectClock(address, LocalDateTime.of(2024,12,27,14,56,0))));
        // System.out.println("抄读自动上报一类数据任务参数：" + Hex.encodeHexString(encodeQueryParams(address, new Fn(1, 30))));
        // System.out.println("抄读自动上报一类数据任务启动/停止状态：" + Hex.encodeHexString(encodeQueryParams(address, new Fn(0, 31))));
        // System.out.println(new Fn(objectToByteArray("01012003")));

        long timestamp = 1735630516545L;
        Instant instant = Instant.ofEpochMilli(timestamp);
        System.out.println(LocalDateTime.ofInstant(instant, ZoneId.systemDefault()));
    }

    /**
     * 抄一个表的命令
     *
     * @param concentratorAddr 集中器地址
     * @param meterSeq         表序号
     * @return 命令
     */
    public static byte[] getCommandDataForMeterReadingOne(String concentratorAddr, String meterSeq) {
        Address address = new Address(concentratorAddr, false);
        Copy copy = new Copy();
        copy.setCopyType((byte) 0x01);
        copy.setMeterQty(1);
        MeterData meterData = new MeterData();
        meterData.setSeq(Integer.parseInt(meterSeq.split("-")[1]));
        copy.setMeterData(new MeterData[]{meterData});
        return encodeSingleCopyCommand(copy, address);
    }

    /**
     * 抄多个表的命令
     *
     * @param command 命令
     * @return 命令
     */
    public static byte[] getCommandDataForMultiCopy(ConcentratorCommand command) {
        if (command == null || StringUtils.isBlank(command.getConcentratorId()) || StringUtils.isBlank(command.getMeter())) {
            throw new IllegalArgumentException("无效的命令参数：" + command);
        }
        Address address = new Address(command.getConcentratorId(), false);
        String[] meters = command.getMeter().split(",");
        // 每次最多抄读5个表
        int maxCopyQty = 5;
        if (meters.length > maxCopyQty) {
            throw new IllegalArgumentException("每次最多抄读5个表");
        }
        Copy copy = new Copy();
        copy.setCopyType((byte) 0x01);
        copy.setMeterQty(meters.length);
        MeterData[] meterDatas = new MeterData[meters.length];
        for (int i = 0; i < meters.length; i++) {
            String meterSeq = meters[i];
            MeterData meterData = new MeterData();
            meterData.setSeq(Integer.parseInt(meterSeq.split("-")[1]));
            meterDatas[i] = meterData;
        }
        copy.setMeterData(meterDatas);
        return encodeSingleCopyCommand(copy, address);
    }

    /**
     * 下载档案每次最多10个表
     *
     * @param command 命令
     * @return 命令
     */
    public static List<byte[]> getCommandDataForDownloadProfile(ConcentratorCommand command) {
        if (command == null || StringUtils.isBlank(command.getConcentratorId()) || CollectionUtils.isEmpty(command.getTerminalsXbs())) {
            throw new IllegalArgumentException("无效的命令参数：" + command);
        }
        List<TerminalsXb> terminalsXbs = command.getTerminalsXbs();
        terminalsXbs.sort(Comparator.comparingInt(e -> Integer.parseInt(e.getCommunicateAddress().split("-")[1])));
        List<DeviceProfile> deviceProfileList = new ArrayList<>(terminalsXbs.size());
        for (TerminalsXb xb : terminalsXbs) {
            DeviceProfile profile = new DeviceProfile();
            profile.setSeq(Integer.parseInt(xb.getCommunicateAddress().split("-")[1]));
            profile.setMeterNumber(xb.getTerminalId());
            profile.setMeasuringPointProperties(new MeasuringPointProperties(MeasuringPointPropertiesG.COLD_WATER_METER, MeasuringPointPropertiesH.MBUS_METER));
            profile.setWiringType(new WiringType(WiringTypeG.SINGLE_PHASE_METER, WiringTypeS.DIRECT));
            profile.setMeterTypeCode(new MeterTypeCode((byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0));
            profile.setBoxNumber((short) 0);
            profile.setCollectingModuleNumber("000000000001");
            deviceProfileList.add(profile);
        }
        return encodeDownloadProfileCommand(deviceProfileList, new Address(command.getConcentratorId(), false));
    }

    /**
     * 设置自动抄表上报时间及周期
     *
     * @param command 命令
     * @return 命令
     */
    public static byte[] getCommandDataForFrequencyControl(ConcentratorCommand command) {
        LocalDateTime dateTime = LocalDateTime.parse(command.getSendTime(), DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
        return encodeAutoReportingTime(new Address(command.getConcentratorId(), false), dateTime, command.getFrequency(), command.getFrequencyUnit());
    }

    /**
     * 集中器格式化
     *
     * @param address 集中器地址
     * @return 命令
     */
    public static byte[] getCommandDataForConcentratorFormat(String address) {
        return encodeResetCommand(new Address(address, false), new Fn(0, 2));
    }

    /**
     * 集中器校时
     *
     * @param address 集中器地址
     * @return 命令
     */
    public static byte[] getCommandDataForSetConcentratorDate(String address) {
        return encodeCorrectClock(new Address(address, false), LocalDateTime.now());
    }

    /**
     * 阀控
     *
     * @param address   集中器地址
     * @param meterAddr 表地址
     * @param isOpen    1开2关
     * @return 命令
     */
    public static byte[] getCommandDataForValveControl(String address, String meterAddr, String isOpen) {
        return encodeValveControl(new Address(address, false), "123456", Integer.parseInt(meterAddr.split("-")[1]), (byte) 0, "01111111", null, null, null, "1".equals(isOpen));
    }

    /**
     * 设置集中器地址
     *
     * @param address    集中器地址
     * @param newAddress 新集中器地址
     * @return 命令
     */
    public static byte[] getCommandDataForSetConcentratorAddress(String address, String newAddress) {
        return encodeSetConcentratorAddr(new Address(address, false), new Address(newAddress, false));
    }

    /**
     * 设置启停自动上报
     *
     * @param command 命令
     * @return 命令
     */
    public static byte[] getSetAutoReport(ConcentratorCommand command) {
        return encodeAutoReporting(new Address(command.getConcentratorId(), false), command.getEnable());
    }

    /**
     * 将十进制数转为二进制，并格式化输出字符串长度到<code>digitsNum</code>，高位不足补<code>0</code>
     * <li><code>toBinaryString(4, 1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0001</code></li>
     * <li><code>toBinaryString(7, 3)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0000011</code></li>
     * 如果<code>number</code>的二进制字符串长度超过<code>digitsNum</code>，则输出的字符串不作改变
     * <li><code>toBinaryString(4, 255)&nbsp;&nbsp;&nbsp;&nbsp;11111111</code></li>
     *
     * @param digitsNum 位个数，高位不足补<code>0</code>
     * @param number    十进制数
     * @return 二进制字符串
     */
    private static String toBinaryString(int digitsNum, int number) {
        return String.format("%" + digitsNum + "s", Integer.toBinaryString(number)).replace(' ', '0');
    }


    /**
     * 帧校验和是用户数据区所有字节的八位位组算术和，不考虑溢出位。用户数据区包括控
     * 制域、地址域、链路用户数据（应用层）三部分。
     *
     * @param bytes 用户数据区所有字节
     * @return 帧校验和
     */
    public static byte checkSum(byte[] bytes) {
        int sum = 0;

        for (byte b : bytes) {
            sum += b & 0xFF;
        }
        return (byte) sum;
    }

    /**
     * 数据格式02转换成数字
     *
     * @param data 数据格式02的字节数组
     * @return 数字
     */
    private static BigDecimal convertData02(byte[] data) {
        int maxLength = 4;
        if (data == null || data.length != maxLength) {
            throw new IllegalArgumentException("参数data不能为null或长度不等于" + maxLength);
        }

        // 十分位、百分位字节
        byte b1 = data[0];
        String bBin1 = toBinaryString(8, b1 & 0xFF);
        // 十分位
        int tenths = Integer.parseInt(bBin1.substring(0, 4), 2);
        // 百分位
        int percentiles = Integer.parseInt(bBin1.substring(4, 8), 2);

        // 十位、个位字节
        byte b2 = data[1];
        String bBin2 = toBinaryString(8, b2 & 0xFF);
        // 十位
        int tens = Integer.parseInt(bBin2.substring(0, 4), 2);
        // 个位
        int bits = Integer.parseInt(bBin2.substring(4, 8), 2);

        // 千位、百位字节
        byte b3 = data[2];
        String bBin3 = toBinaryString(8, b3 & 0xFF);
        // 千位
        int thousands = Integer.parseInt(bBin3.substring(0, 4), 2);
        // 百位
        int hundreds = Integer.parseInt(bBin3.substring(4, 8), 2);

        // 十万位、万位字节
        byte b4 = data[3];
        String bBin4 = toBinaryString(8, b4 & 0xFF);
        // 十万位
        int hundredThousands = Integer.parseInt(bBin4.substring(0, 4), 2);
        // 万位
        int tenThousands = Integer.parseInt(bBin4.substring(4, 8), 2);
        String number = hundredThousands + String.valueOf(tenThousands) + thousands + hundreds + tens + bits + "." + tenths + percentiles;
        return new BigDecimal(number);
    }

    /**
     * 时间转成数据格式01
     *
     * @param dateTime 日期时间
     * @return byte[]
     */
    private static byte[] toData01(LocalDateTime dateTime) {
        byte[] result = new byte[6];
        // 秒：D7~D4 BCD码十位；D3~D0 BCD码个位
        String second = String.format("%02d", dateTime.getSecond());
        System.arraycopy(convertToBcd(second), 0, result, 0, 1);
        // 分：D7~D4 BCD码十位；D3~D0 BCD码个位
        String minute = String.format("%02d", dateTime.getMinute());
        System.arraycopy(convertToBcd(minute), 0, result, 1, 1);
        // 时：D7~D4 BCD码十位；D3~D0 BCD码个位
        String hour = String.format("%02d", dateTime.getHour());
        System.arraycopy(convertToBcd(hour), 0, result, 2, 1);
        // 日：D7~D4 BCD码十位；D3~D0 BCD码个位
        String day = String.format("%02d", dateTime.getDayOfMonth());
        System.arraycopy(convertToBcd(day), 0, result, 3, 1);
        // 星期1~7（周一~周日）：D7~D5。月：D4 十位；D3~D0 个位
        int weekday = dateTime.getDayOfWeek().getValue();
        int month = dateTime.getMonthValue();
        String bin = toBinaryString(3, weekday) + (month / 10) + toBinaryString(4, month % 10);
        result[4] = (byte) Integer.parseInt(bin, 2);
        // 年：D7~D4 BCD码十位；D3~D0 BCD码个位
        String year = String.valueOf(dateTime.getYear()).substring(2, 4);
        System.arraycopy(convertToBcd(year), 0, result, 5, 1);
        return result;
    }

    /**
     * 数据格式01转换成LocalDateTime
     *
     * @param data 数据格式01的字节数组
     * @return LocalDateTime
     */
    private static LocalDateTime convertData01(byte[] data) {
        String hexStr = Hex.encodeHexString(data);
        // 秒：D7~D4 BCD码十位；D3~D0 BCD码个位
        int second = Integer.parseInt(hexStr.substring(0, 2));
        // 分：D7~D4 BCD码十位；D3~D0 BCD码个位
        int minute = Integer.parseInt(hexStr.substring(2, 4));
        // 时：D7~D4 BCD码十位；D3~D0 BCD码个位
        int hour = Integer.parseInt(hexStr.substring(4, 6));
        // 日：D7~D4 BCD码十位；D3~D0 BCD码个位
        int day = Integer.parseInt(hexStr.substring(6, 8));
        // 星期1~7（周一~周日）：D7~D5。月：D4 十位；D3~D0 个位
        String weekdayAndMonthBinStr = toBinaryString(8, Integer.parseInt(hexStr.substring(8, 10), 16));
        int month = Integer.parseInt(weekdayAndMonthBinStr.substring(3, 4) + Integer.parseInt(weekdayAndMonthBinStr.substring(4, 8), 2));
        // 年：D7~D4 BCD码十位；D3~D0 BCD码个位
        int year = Integer.parseInt(hexStr.substring(10, 12)) + 2000;
        return LocalDateTime.of(year, month, day, hour, minute, second);
    }

    /**
     * 表号或采集模块编号转成数据格式03
     *
     * @param number 表号或采集模块编号
     * @return byte[]
     */
    private static byte[] toData03(String number) {
        int maxLength = 12;
        // 14位表号开头是10，需要去掉
        int length14 = 14;
        String prefix = "10";
        if (StringUtils.isBlank(number)) {
            number = "1";
        } else if (StringUtils.startsWith(number, prefix) && number.length() == length14) {
            // 14位表号开头是10，需要去掉
            number = number.substring(2);
        } else if (number.length() > maxLength) {
            throw new IllegalArgumentException("参数number不能为空或长度大于" + maxLength);
        }
        long num;
        try {
            num = Long.parseLong(number);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("参数number只能是纯数字");
        }
        byte[] result = new byte[6];
        // 把数字补到12位，不足高位补0
        String format = String.format("%012d", num);
        // 两个数字一组分割
        int groupSize = 2;
        for (int i = 0; i < format.length() / groupSize; i++) {
            // 每组2位
            String digit = format.substring(i * 2, i * 2 + 2);
            byte[] bcdBytes = convertToBcd(digit);
            // 小端字节序，高位在后，低位在前
            result[6 - 1 - i] = bcdBytes[0];
        }
        return result;
    }

    /**
     * 测量点性质转data06格式
     *
     * @param properties 测量点性质
     * @return byte
     */
    private static byte toData06(MeasuringPointProperties properties) {
        if (properties == null) {
            return 0;
        }
        String binary = "0" +
                properties.getG().getG2() +
                properties.getG().getG1() +
                properties.getG().getG0() +
                "0" +
                properties.getH().getH2() +
                properties.getH().getH1() +
                properties.getH().getH0();
        return (byte) Integer.parseInt(binary, 2);
    }

    /**
     * 接线方式转data07格式
     *
     * @param wiringType 接线方式
     * @return byte
     */
    private static byte toData07(WiringType wiringType) {
        if (wiringType == null) {
            return 0;
        }
        String binary = "000000" +
                wiringType.getG().getG() +
                wiringType.getS().getS();
        return (byte) Integer.parseInt(binary, 2);
    }

    /**
     * 电表费率号转data08格式
     *
     * @param meterFeeRateNumber 电表费率号
     * @return byte
     */
    private static byte toData08(MeterFeeRateNumber meterFeeRateNumber) {
        if (meterFeeRateNumber == null) {
            return 0;
        }
        String binary = meterFeeRateNumber.getSBinary() + meterFeeRateNumber.getGBinary();
        return (byte) Integer.parseInt(binary, 2);
    }

    /**
     * 表类型代码转data09格式
     *
     * @param meterTypeCode 表类型代码
     * @return byte
     */
    private static byte toData09(MeterTypeCode meterTypeCode) {
        if (meterTypeCode == null) {
            return 0;
        }
        String binary = "00" +
                meterTypeCode.getF() +
                meterTypeCode.getK() +
                meterTypeCode.getH() +
                meterTypeCode.getS() +
                meterTypeCode.getGBinary();
        return (byte) Integer.parseInt(binary, 2);
    }

    /**
     * 线路编号转data10格式
     *
     * @param lineNumber 表类型代码
     * @return byte[]
     */
    private static byte[] toData10(LineNumber lineNumber) {
        byte[] result = new byte[2];
        if (lineNumber != null) {
            result[0] = convertToBcd(String.valueOf(lineNumber.getSecondLineNumber()) + String.valueOf(lineNumber.getThirdLineNumber()))[0];
            result[1] = convertToBcd(String.valueOf(lineNumber.getPrimaryLineNumber()) + String.valueOf(lineNumber.getFirstLineNumber()))[0];
        }
        return result;
    }


    /**
     * 判断是否是无数据，无数据返回<code>false</code>，有数据返回<code>true</code><br>
     * 终端在响应主站对终端的参数或数据请求时，如终端无某项数据，且该“无某项数据”的信息需主站辨识，则终端应将该数据项内容的每个字节填写“0EEH”。
     *
     * @param data 数据
     * @return boolean
     */
    public static boolean isNotEmptyData(byte[] data) {
        for (byte b : data) {
            if (b != (byte) 0xEE) {
                // 如果遇到不是 0xEE 的字节，则返回 true
                return true;
            }
        }
        // 如果全部字节都是 0xEE，则返回 false
        return false;
    }
}
